package com.dp.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.dp.dto.Result;
import com.dp.entity.ShopType;
import com.dp.mapper.ShopTypeMapper;
import com.dp.service.IShopTypeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dp.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import static com.dp.utils.RedisConstants.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //将list按String存储
    @Override
    public List<ShopType> getShopType() {
        String key = SHOP_TYPE_LIST;
//        首先判断redis中是否有数据，是否命中
        String s = stringRedisTemplate.opsForValue().get(key);
        if (!StrUtil.isBlank(s)) {//不为空
//            将String转成list
            List<ShopType> shopTypes = JSONUtil.toList(s, ShopType.class);
            return shopTypes;
        }
//        判断命中是否是空值
        if (s != null) {//经过上面判断，不是null,就一定是空字符串
            return null;
        }
        String lockKey = SHOP_TYPE_LOCK;
        List<ShopType> typeList = null;
//        获取lock
        try {
            boolean b = tryLock(lockKey);
            if (!b) {
//            获取失败，休眠，递归尝试再次获取
                Thread.sleep(50);
                return getShopType();
            }
//        获取成功，查找数据库
            typeList= this.query().orderByAsc("sort").list();
            if (typeList == null) {
//           存储空对象
                stringRedisTemplate.opsForValue().set(SHOP_TYPE_LIST, "", SHOP_TYPE_NULL, TimeUnit.MINUTES);
                return typeList;
            }
//        保存到redis
            String type = JSONUtil.toJsonStr(typeList);
            stringRedisTemplate.opsForValue().set(SHOP_TYPE_LIST, type, SHOP_TYPE_TIME, TimeUnit.MINUTES);

        }catch (Exception e){
            return null;
        }finally {
            //        释放锁
            unlock(SHOP_TYPE_LOCK);
        }
        return typeList;
    }

    //尝试获取锁
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//相当于setnx,当值存在时，不会进行修改
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
//    线程池
    private static final ExecutorService CACHE_REBULD = Executors
        .newFixedThreadPool(10);
//    逻辑删除解决缓存击穿
    public List<ShopType> getShopType2() {
//        1.从redis中查询缓存
        String key = SHOP_TYPE_LIST;
        String lockKey = SHOP_TYPE_LOCK;
//        2.判断是否存在
//        首先判断redis中是否有数据，是否命中
        String s = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(s)) {//为空，直接返回null
            System.out.println("为空");
            return null;
        }
//        3.命中，将redis中redisData对象反序列化出来
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
//        拿到用户信息
        List<ShopType> shopTypes = JSONUtil.toList((JSONArray)redisData.getData(), ShopType.class);
//        拿到过期时间
        LocalDateTime expireTime = redisData.getExpireTime();
//        4.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){//是否在当前时间之后
//        之后说明已经没有过期过期
            System.out.println("未过期");
//        未过期，直接返回相应信息
            return shopTypes;
        }
//        5.过期，缓存重建
//        获取互斥锁
        boolean b = tryLock(lockKey);
        System.out.println(b);
//        判断是否获取成功
        if(b){
//        成功开启独立线程进行缓存重建
            CACHE_REBULD.submit(()->{
                try {
                    System.out.println("开始重建缓存");
                    this.sendSomeExample();
                }catch (Exception e){
                    throw new RuntimeException("逻辑删除新线程的缓存重建失败");
                }finally {
                    //            释放锁
                    this.unlock(lockKey);
                }
            });
        }
//        获取失败返回过期对象
        return shopTypes;
    }
    public void sendSomeExample(){
        List<ShopType> shopTypes = this.query().orderByAsc("sort").list();
        String key = SHOP_TYPE_LIST;
        RedisData redisData1 = new RedisData();
        redisData1.setData(shopTypes);
        redisData1.setExpireTime(LocalDateTime.now().plusSeconds(EXPIRE_TIME));
//               写入redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData1));
    }
}
